# vim:ts=4:sw=4:noet:
""" Parser """

import ir
import primtable as primtable
from lexer import Lexer, Token

class Parser:

	def __init__(self, mem, status):
		self.status = status
		self.mainClass = None
		self.prims = primtable.PrimTable()

	def acceptType(self, type):
		if type!=self.lex.token.type:
			self.status.error("Expected %s, got %s" % (
				Token.prettyTypenames[type], 
				Token.prettyTypenames[self.lex.token.type]), 
				self.lex.popLine())
		t = self.lex.token
		self.lex.nextLex()
		return t
	
	def accept(self, s, t=None):
		if s!=self.lex.token.value:
			self.status.error("Expected '%s', got '%s'" % (
				s, self.lex.token.value), 
				self.lex.popLine())
		if t and t!=self.lex.token.type:
			self.status.error("Expected %s, got %s" % (
				Token.prettyTypenames[t], 
				Token.prettyTypenames[self.lex.token.type]), 
				self.lex.popLine())
		tok = self.lex.token
		self.lex.nextLex()
		return tok
	
	def bootMethod(self, mainClassName):
		boottext = "boot [ (mainClass new) main. <halt> ]"
		self.lex = Lexer(self.status, boottext)
		self.lex.nextLex()
		self.ivars = []
		m = self.parseMethod(None)
		return m

	def parseFile(self, filename):
		f = file(filename,"rb")
		source = f.read()
		f.close()
		return self.parseFileFromSource(source)

	def parseFileFromSource(self, source):
		self.lex = Lexer(self.status, source)
		self.lex.nextLex()
		classes = []
		while self.lex.token and self.lex.token.type==Token.ID:
			classes.append(self.parseClass())
		return classes
	
	def parseVars(self):
		vars = []
		if self.lex.token.value=="|":
			self.accept("|", Token.BIN)
			while self.lex.token.value!="|":
				vars.append(self.acceptType(Token.ID).value)
			self.accept("|", Token.BIN)
		return vars

	def parsePragma(self):
		self.accept("<", Token.BIN)
		k = self.acceptType(Token.ID).value[:-1]
		v = self.acceptType(Token.STR).value
		self.accept(">", Token.BIN)
		return k,v

	def parsePragmas(self):
		pragmas = {}
		while self.lex.token.value=="<":
			self.lex.saveState()
			self.lex.nextLex()
			if self.lex.token.value!="category:":
				self.lex.backtrack()
				break
			self.lex.backtrack()
			k,v = self.parsePragma()
			pragmas[k] = v
		return pragmas
	
	def parseClass(self):
		lnumStart = self.lex.lineNum
		self.lex.pushLine()
		docs = self.lex.lastComment
		if len(docs) and docs[0]!="!":
			docs=""
		else:
			docs = docs[2:]
		self.lex.lastComment = ""
		supername = self.acceptType(Token.ID)
		self.accept("subclass:")
		name = self.acceptType(Token.ID)
		self.accept("[", Token.BIN)
		tivars = self.parseVars()
		pragmas = self.parsePragmas()
		meths = []
		cls = ir.IRClass(
						0,
						name.value, 
						supername.value, 
						tivars, 
						meths, 
						docs, 
						pragmas)
		while self.lex.token.type!=Token.BIN or self.lex.token.value!="]":
			meths.append(self.parseMethod(cls))
		self.lex.lastComment = ""
		self.accept("]", Token.BIN)
		cls.line = self.lex.popLine()
		return cls

	def __parseMethod(self, doCleanSource=True):
		self.lex.pushLine()
		self.temps = []
		self.maxTemps = 0
		docs = self.lex.lastComment
		if len(docs) and docs[0]!="!":
			docs=""
		else:
			docs = docs[2:]
		name = self.parseMethodName()
		self.lex.idxofs = self.lex.index+1
		self.accept("[", Token.BIN)
		tokStart = self.lex.token
		for t in self.parseVars():
			self.addTempName(t)
		body = self.parseBody()
		tokEnd = self.lex.oldtoken
		self.lex.lastComment = ""
		self.accept("]", Token.BIN)
		source = self.lex.getText(tokStart,tokEnd)
		if doCleanSource:
			source = self.cleanSource(source)
		return ir.IRMethod(
							self.lex.popLine(),
							name, 
							self.args, 
							self.temps, 
							self.maxTemps, 
							body,
							source,
							docs)
	
	def parseMethodFromSource(self, source):
		self.lex = Lexer(self.status, source)
		self.lex.nextLex()
		irmeth = self.__parseMethod(False)
		self.acceptType(Token.EOF)
		return irmeth

	def parseMethod(self, cls):
		irmeth = self.__parseMethod()
		if irmeth.name=="main":
			self.mainClass = cls
		return irmeth

	def cleanSource(self, src):
		doDetab = True
		numtabs = 100
		for sl in src.split("\n")[1:]:
			mtl = 100
			if len(sl)>0:
				for i in range(len(sl)):
					if sl[i]!="\t":
						break
				mtl = i
				if mtl<numtabs:
					numtabs = mtl
		nsrc = ""
		if numtabs!=100 and numtabs>0:
			print "detabbing:\n", src
			nsrc = src.split("\n")[0]+"\n"
			for sl in src.split("\n")[1:]:
				nsrc += "%s\n" % sl[numtabs:]
			src = nsrc[:-1]
			print src
		return src

	def parseMethodName(self):
		self.args = ["self"]
		if self.lex.token.isName():
			return self.acceptType(Token.ID).value
		if self.lex.token.isBinary():
			name = self.accept(self.lex.token.value).value
			self.args.append(self.acceptType(Token.ID).value)
			return name
		if self.lex.token.isKeyword():
			name = ""
			while self.lex.token.isKeyword():
				key = self.acceptType(Token.ID)
				name += key.value
				self.args.append(self.acceptType(Token.ID).value)
			return name
		self.status.error("Invalid method name")

	def parseBody(self):
		lnum = self.lex.lineNum
		self.lex.pushLine()
		sl = self.parseStatementList()
		return ir.IRBody(self.lex.popLine(), sl)

	def parseBlock(self):
		lnum = self.lex.lineNum
		self.lex.pushLine()
		self.lex.nextLex()
		btemps = []
		if self.lex.token.value[0]==":":
			btemps = self.parseBlockTemporaries()
		stmts = self.parseStatementList()
		self.accept("]", Token.BIN)
		return ir.IRBlock(self.lex.popLine(), btemps, stmts)

	def addTempName(self, name):
		if ((name in self.args) or (name in self.temps)):
			self.status.error("Name redefined: %s" % (name), self.lex.popLine())
		self.temps.append(name)
		self.maxTemps += 1

	def parseBlockTemporaries(self):
		bts = []
		while self.lex.token.value[0]==":":
			self.lex.nextLex()
			if self.lex.token.isName():
				self.maxTemps += 1 # FIXME: used to add to self.temps, what does this change?
				bts.append(self.lex.token.value)
			else:
				self.status.error("Invalid block arg: %s" % (
					self.lex.token.value), 
					self.lex.popLine())
			self.lex.nextLex()
		self.accept("|", Token.BIN)
		return bts

	def parseStatementList(self):
		list = []
		while self.lex.token.value!="]":
			list.append(self.parseStatement())
			if self.lex.token.value==".":
				self.accept(".")
		return list

	def parseStatement(self):
		if self.lex.token.value == "<":
			return self.parsePrimitive()
		elif self.lex.token.value=="^":
			self.lex.pushLine()
			self.accept("^", Token.BIN)
			lnum = self.lex.lineNum
			exp = self.parseExpression()
			return ir.IRReturn(self.lex.popLine(), exp)
		else:
			return self.parseExpression()

	def parseExpression(self):
		if not self.lex.token.isName():
			return self.parseCascade(self.parseTerm())
		node = self.nameNode()
		if self.lex.token.value == ":=":
			self.lex.pushLine()
			self.accept(self.lex.token.value, Token.BIN)
			lnum = self.lex.lineNum
			exp = self.parseExpression()
			return ir.IRAssign(self.lex.popLine(), node, exp)
		return self.parseCascade(node)

	def parseCascade(self, base):
		node = self.parseKeywordContinuation(base)
		if self.lex.token.value == ";" and isinstance(node, ir.IRMessage):
			lnum = self.lex.lineNum
			self.lex.pushLine()
			recv = node.recv
			node.recv = None
			list = [node]
			while self.lex.token.value == ";":
				self.lex.nextLex()
				list.append(self.parseKeywordContinuation(None))
			node = ir.IRCascade(self.lex.popLine(), recv)
			node.list = list
		return node

	def parseKeywordContinuation(self, base):
		recv = self.parseBinaryContinuation(base)
		if not self.lex.token.isKeyword():
			return recv
		name = ""
		args = []
		lnum = self.lex.lineNum
		self.lex.pushLine()
		while self.lex.token.isKeyword():
			name = name + self.lex.token.value
			self.lex.nextLex()
			args.append(self.parseBinaryContinuation(self.parseTerm()))
		return ir.IRMessage(self.lex.popLine(), recv, name, args)

	def parseBinaryContinuation(self, base):
		recv = self.parseUnaryContinuation(base)
		while self.lex.token.isBinary():
			self.lex.pushLine()
			lnum = self.lex.lineNum
			name = self.lex.token.value
			self.lex.nextLex()
			arg = self.parseUnaryContinuation(self.parseTerm())
			recv = ir.IRMessage(self.lex.popLine(), recv, name, [arg])
		return recv

	def parseUnaryContinuation(self, base):
		recv = base
		while self.lex.token.isName():
			self.lex.pushLine()
			lnum = self.lex.lineNum
			name = self.lex.token.value
			self.lex.nextLex()
			recv = ir.IRMessage(self.lex.popLine(), recv, name, [])
		return recv

	def parseTerm(self):
		if self.lex.token.value == None:
			self.status.error("Unexpected end of input",
				self.lex.popLine())
		elif self.lex.token.type == Token.BIN and self.lex.token.value == "(":
			self.lex.nextLex()
			node = self.parseExpression()
			self.accept(")", Token.BIN)
			return node
		elif self.lex.token.type == Token.BIN and self.lex.token.value == "[":
			return self.parseBlock()
		elif self.lex.token.isName():
			return self.nameNode()
		else:
			return self.parseLiteral()
	
	def parseLiteral(self):
		self.lex.pushLine()
		if len(self.lex.token.value)>0 and self.lex.token.value[0] == "$":
			t = self.lex.currentChar()
			start = self.lex.token.index
			self.lex.nextChar()
			self.lex.nextLex()
			return ir.IRChar(self.lex.popLine(), t)
		elif ((self.lex.token.type == Token.INT and 
			   len(self.lex.token.value)>0 and 
			   self.lex.token.value[0].isdigit()) or
			  (self.lex.token.type == Token.BIN and self.lex.token.value == "-")):
			return self.parseInteger()
		elif self.lex.token.type == Token.STR:
			v = self.lex.token.value
			self.lex.nextLex()
			return ir.IRString(self.lex.popLine(), v)
		elif self.lex.token.type == Token.SYM:
			v = self.lex.token.value
			self.lex.nextLex()
			return ir.IRSymbol(self.lex.popLine(), v)
		elif self.lex.token.value == "#(":
			arr = []
			self.accept("#(", Token.BIN)
			while self.lex.token.value!=")":
				arr.append(self.parseLiteral())
			self.accept(")", Token.BIN)
			return ir.IRArray(self.lex.popLine(), arr)
		else:
			v = self.lex.token.value
			self.lex.nextLex()
			self.status.error("Invalid literal: %s" % (v),
				self.lex.popLine())

	def parseInteger(self):
		neg = False
		if self.lex.token.type == Token.BIN and self.lex.token.value == "-":
			neg = True
			self.lex.nextLex()
		
		iv = self.lex.token.value
		self.lex.nextLex()
		if "r" in iv:
			if neg:
				self.status.error("Negative arbitrary based integer invalid", self.lex.popLine())
			base, val = iv.split("r")
			v = int(val, int(base))
			if v>(2**32 - 1):
				self.status.error("Integer overflow", self.lex.popLine())
		else:
			for c in iv:
				if not c.isdigit():
					self.status.error("Expected number literal", self.lex.popLine())
			v = int(iv)
			if neg:
				v = -v
		return ir.IRInteger(self.lex.popLine(), v)

	def parsePrimitive(self):
		lnum = self.lex.lineNum
		self.lex.pushLine()
		start = self.lex.token.index
		self.lex.nextLex()
		name = self.lex.token.value
		if not self.lex.token.isName():
			self.status.error("error parsing primitive", self.lex.popLine())
		self.lex.nextLex()
		num = self.prims.lookupName(name)
		if num == None:
			self.status.error("Invalid primitive '%s'" % (name), self.lex.popLine())
		args = [] 
		while self.lex.token.value!=">":
			args.append(self.parseTerm())
		size = self.lex.token.index - start + 1
		self.lex.nextLex()
		return ir.IRPrimitive(self.lex.popLine(), num, args)

	def nameNode(self):
		self.lex.pushLine()
		v = self.lex.token.value
		self.lex.nextLex()
		return ir.IRName(self.lex.popLine(), v)
